// 在开发过程中通常会使用JSON.parse(JSON.stringify(obj))进行拷贝
// 其过程就是利用JSON.stringify 将js对象序列化（JSON字符串），再使用JSON.parse来反序列化(还原)js对象；
// 但是这种方法，只适用于基本数据类型的拷贝，遇到如下几种情况会有问题，需要注意：
// 1. 如果json里面有时间对象，则序列化后会将时间对象转换为字符串格式
// let obj = {
//     motto: '行则将至',
//     date: new Date()
// }
// let deepCopy = JSON.parse(JSON.stringify(obj))
// console.log(obj) //{motto: '行则将至', date: Tue Mar 29 2022 16:47:33 GMT+0800 (中国标准时间)}
// console.log(deepCopy) //{motto: '行则将至', date: '2022-03-29T08:47:33.399Z'}
// console.log(typeof obj.date);
// console.log(typeof deepCopy.date);
// 2.如果json里有 function,undefined，则序列化后会将 function,undefined 丢失
// let obj = {
//     motto: '行则将至',
//     typeU: undefined,
//     fun: function() {
//         console.log('fun');
//     },
// }
// let deepCopy = JSON.parse(JSON.stringify(obj))
// console.log(obj) //{motto: '行则将至', typeU: undefined, fun: ƒ}
// console.log(deepCopy) //{motto: '行则将至'}
// 3.如果json里有NaN、Infinity和-Infinity，则序列化后会变成null
// let obj = {
//     motto: '行则将至',
//     type1: NaN,
//     infinity1: 1.7976931348623157E+10308,
//     infiniteSimal: -1.7976931348623157E+10308
// }
// let deepCopy = JSON.parse(JSON.stringify(obj))
// console.log(obj) //{motto: '行则将至', type1: NaN, infinity1: Infinity, infiniteSimal: -Infinity}
// console.log(deepCopy) //{motto: '行则将至', type1: null, infinity1: null, infiniteSimal: null}
// 4.如果json里有对象是由构造函数生成的，则序列化的结果会丢弃对象的 constructor
// function Person(name) {
//     this.name = name;
// }
// let obj = {
//     p1: new Person('喜陈'),
//     motto: '行则将至'
// };
// let deepCopy = JSON.parse(JSON.stringify(obj));
// console.log('obj', obj);
// console.log('deepCopy', deepCopy);
// // 查看原型链
// console.log(obj.p1.__proto__.constructor === Person); // true
// console.log(deepCopy.p1.__proto__.constructor === Object); // true
// 5.如果对象中存在循环引用的情况将抛出错误
// let obj = {
//     motto: '行则将至',
// }
// obj.obj = obj
// let deepCopy = JSON.parse(JSON.stringify(obj))

// 深拷贝：在堆内存中重新开辟一个存储空间，完全克隆一个一模一样的对象
// 总结出下面方法是最优解，能hold住 new date()和undefined等情况
function deepCopy(obj, cache = []) {
    if (obj === null || typeof obj != 'object') {
        return obj
    }
    if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
    if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
    if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
    const item = cache.filter(item => item.origin === obj)[0]
    if (item) return item.newObj

    const newObj = Array.isArray(obj) ? [] : {}
    cache.push({
        origin: obj,
        newObj
    })
    Object.keys(obj).forEach(key => {
        if (typeof obj[key] == "object") {
            newObj[key] = this.deepCopy(obj[key], cache)
        } else {
            newObj[key] = obj[key]
        }
    });
    return newObj
}

let obj = {
    num: 1,
    str: "string",
    booleans: true,
    date: new Date(),
    nullObj: null,
    arr: [1, 2, 3, 4],
    und: undefined,
    func: () => {
        console.log('1111');
    },
    childObj: {
        num: 1,
        str: "string",
        booleans: true,
        date: new Date(),
        nullObj: null,
        arr: [1, 2, 3, 4],
        und: undefined,
        func: () => {
            console.log('1111');
        },
    },
}

let copyObj = deepCopy(obj);
copyObj.arr[0] = 2;
console.log("copyObj", copyObj);
console.log("obj", obj);